home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / editor.js < prev    next >
Text File  |  2010-01-15  |  34KB  |  1,152 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const saveTimeout = 400;
  9. const pageAmount = 10;
  10.  
  11. // ************************************************************************************************
  12. // Globals
  13.  
  14. var currentTarget = null;
  15. var currentGroup = null;
  16. var currentPanel = null;
  17. var currentEditor = null;
  18.  
  19. var defaultEditor = null;
  20.  
  21. var originalClassName = null;
  22.  
  23. var originalValue = null;
  24. var defaultValue = null;
  25. var previousValue = null;
  26.  
  27. var invalidEditor = false;
  28. var ignoreNextInput = false;
  29.  
  30. // ************************************************************************************************
  31.  
  32. Firebug.Editor = extend(Firebug.Module,
  33. {
  34.     supportsStopEvent: true,
  35.  
  36.     dispatchName: "editor",
  37.     tabCharacter: "    ",
  38.  
  39.     startEditing: function(target, value, editor)
  40.     {
  41.         this.stopEditing();
  42.  
  43.         if (hasClass(target, "insertBefore") || hasClass(target, "insertAfter"))
  44.             return;
  45.  
  46.         var panel = Firebug.getElementPanel(target);
  47.         if (!panel.editable)
  48.             return;
  49.  
  50.         defaultValue = target.getAttribute("defaultValue");
  51.         if (value == undefined)
  52.         {
  53.             value = target.textContent;
  54.             if (value == defaultValue)
  55.                 value = "";
  56.         }
  57.  
  58.         originalValue = previousValue = value;
  59.  
  60.         invalidEditor = false;
  61.         currentTarget = target;
  62.         currentPanel = panel;
  63.         currentGroup = getAncestorByClass(target, "editGroup");
  64.  
  65.         currentPanel.editing = true;
  66.  
  67.         var panelEditor = currentPanel.getEditor(target, value);
  68.         currentEditor = editor ? editor : panelEditor;
  69.         if (!currentEditor)
  70.             currentEditor = getDefaultEditor(currentPanel);
  71.  
  72.         var inlineParent = getInlineParent(target);
  73.         var targetSize = getOffsetSize(inlineParent);
  74.  
  75.         setClass(panel.panelNode, "editing");
  76.         setClass(target, "editing");
  77.         if (currentGroup)
  78.             setClass(currentGroup, "editing");
  79.  
  80.         currentEditor.show(target, currentPanel, value, targetSize);
  81.         dispatch(this.fbListeners, "onBeginEditing", [currentPanel, currentEditor, target, value]);
  82.         currentEditor.beginEditing(target, value);
  83.         this.attachListeners(currentEditor, panel.context);
  84.     },
  85.  
  86.     stopEditing: function(cancel)
  87.     {
  88.         if (!currentTarget)
  89.             return;
  90.  
  91.         clearTimeout(this.saveTimeout);
  92.         delete this.saveTimeout;
  93.  
  94.         this.detachListeners(currentEditor, currentPanel.context);
  95.  
  96.         removeClass(currentPanel.panelNode, "editing");
  97.         removeClass(currentTarget, "editing");
  98.         if (currentGroup)
  99.             removeClass(currentGroup, "editing");
  100.  
  101.         var value = currentEditor.getValue();
  102.         if (value == defaultValue)
  103.             value = "";
  104.  
  105.         var removeGroup = currentEditor.endEditing(currentTarget, value, cancel);
  106.  
  107.         try
  108.         {
  109.             if (cancel)
  110.             {
  111.                 dispatch([Firebug.A11yModel], 'onInlineEditorClose', [currentPanel, currentTarget, removeGroup && !originalValue]);
  112.                 if (value != originalValue)
  113.                     this.saveEditAndNotifyListeners(currentTarget, originalValue, previousValue);
  114.  
  115.                 if (removeGroup && !originalValue && currentGroup)
  116.                     currentGroup.parentNode.removeChild(currentGroup);
  117.             }
  118.             else if (!value)
  119.             {
  120.                 this.saveEditAndNotifyListeners(currentTarget, null, previousValue);
  121.  
  122.                 if (removeGroup && currentGroup)
  123.                     currentGroup.parentNode.removeChild(currentGroup);
  124.             }
  125.             else
  126.                 this.save(value);
  127.         }
  128.         catch (exc)
  129.         {
  130.             ERROR(exc);
  131.         }
  132.  
  133.         currentEditor.hide();
  134.         currentPanel.editing = false;
  135.  
  136.         dispatch(this.fbListeners, "onStopEdit", [currentPanel, currentEditor, currentTarget]);
  137.         currentTarget = null;
  138.         currentGroup = null;
  139.         currentPanel = null;
  140.         currentEditor = null;
  141.         originalValue = null;
  142.         invalidEditor = false;
  143.  
  144.         return value;
  145.     },
  146.  
  147.     cancelEditing: function()
  148.     {
  149.         return this.stopEditing(true);
  150.     },
  151.  
  152.     update: function(saveNow)
  153.     {
  154.         if (this.saveTimeout)
  155.             clearTimeout(this.saveTimeout);
  156.  
  157.         invalidEditor = true;
  158.  
  159.         currentEditor.layout();
  160.  
  161.         if (saveNow)
  162.             this.save();
  163.         else
  164.         {
  165.             var context = currentPanel.context;
  166.             this.saveTimeout = context.setTimeout(bindFixed(this.save, this), saveTimeout);
  167.         }
  168.     },
  169.  
  170.     save: function(value)
  171.     {
  172.         if (!invalidEditor)
  173.             return;
  174.  
  175.         if (value == undefined)
  176.             value = currentEditor.getValue();
  177.         try
  178.         {
  179.             this.saveEditAndNotifyListeners(currentTarget, value, previousValue);
  180.  
  181.             previousValue = value;
  182.             invalidEditor = false;
  183.         }
  184.         catch (exc)
  185.         {
  186.         }
  187.     },
  188.  
  189.     saveEditAndNotifyListeners: function(currentTarget, value, previousValue)
  190.     {
  191.         currentEditor.saveEdit(currentTarget, value, previousValue);
  192.         dispatch(this.fbListeners, "onSaveEdit", [currentPanel, currentEditor, currentTarget, value, previousValue]);
  193.     },
  194.  
  195.     setEditTarget: function(element)
  196.     {
  197.         if (!element)
  198.         {
  199.             dispatch([Firebug.A11yModel], 'onInlineEditorClose', [currentPanel, currentTarget, true]);
  200.             this.stopEditing();
  201.         }
  202.         else if (hasClass(element, "insertBefore"))
  203.             this.insertRow(element, "before");
  204.         else if (hasClass(element, "insertAfter"))
  205.             this.insertRow(element, "after");
  206.         else
  207.             this.startEditing(element);
  208.     },
  209.  
  210.     tabNextEditor: function()
  211.     {
  212.         if (!currentTarget)
  213.             return;
  214.  
  215.         var value = currentEditor.getValue();
  216.         var nextEditable = currentTarget;
  217.         do
  218.         {
  219.             nextEditable = !value && currentGroup
  220.                 ? getNextOutsider(nextEditable, currentGroup)
  221.                 : getNextByClass(nextEditable, "editable");
  222.         }
  223.         while (nextEditable && !nextEditable.offsetHeight);
  224.  
  225.         this.setEditTarget(nextEditable);
  226.     },
  227.  
  228.     tabPreviousEditor: function()
  229.     {
  230.         if (!currentTarget)
  231.             return;
  232.  
  233.         var value = currentEditor.getValue();
  234.         var prevEditable = currentTarget;
  235.         do
  236.         {
  237.             prevEditable = !value && currentGroup
  238.                 ? getPreviousOutsider(prevEditable, currentGroup)
  239.                 : getPreviousByClass(prevEditable, "editable");
  240.         }
  241.         while (prevEditable && !prevEditable.offsetHeight);
  242.  
  243.         this.setEditTarget(prevEditable);
  244.     },
  245.  
  246.     insertRow: function(relative, insertWhere)
  247.     {
  248.         var group =
  249.             relative || getAncestorByClass(currentTarget, "editGroup") || currentTarget;
  250.         var value = this.stopEditing();
  251.  
  252.         currentPanel = Firebug.getElementPanel(group);
  253.  
  254.         currentEditor = currentPanel.getEditor(group, value);
  255.         if (!currentEditor)
  256.             currentEditor = getDefaultEditor(currentPanel);
  257.  
  258.         currentGroup = currentEditor.insertNewRow(group, insertWhere);
  259.         if (!currentGroup)
  260.             return;
  261.  
  262.         var editable = hasClass(currentGroup, "editable")
  263.             ? currentGroup
  264.             : getNextByClass(currentGroup, "editable");
  265.  
  266.         if (editable)
  267.             this.setEditTarget(editable);
  268.     },
  269.  
  270.     insertRowForObject: function(relative)
  271.     {
  272.         var container = getAncestorByClass(relative, "insertInto");
  273.         if (container)
  274.         {
  275.             relative = getChildByClass(container, "insertBefore");
  276.             if (relative)
  277.                 this.insertRow(relative, "before");
  278.         }
  279.     },
  280.  
  281.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  282.  
  283.     attachListeners: function(editor, context)
  284.     {
  285.         var win = currentTarget.ownerDocument.defaultView;
  286.         win.addEventListener("resize", this.onResize, true);
  287.         win.addEventListener("blur", this.onBlur, true);
  288.  
  289.         var chrome = Firebug.chrome;
  290.  
  291.         this.listeners = [
  292.             chrome.keyCodeListen("ESCAPE", null, bind(this.cancelEditing, this)),
  293.         ];
  294.  
  295.         if (editor.arrowCompletion)
  296.         {
  297.             this.listeners.push(
  298.                 chrome.keyCodeListen("UP", null, bindFixed(editor.completeValue, editor, -1)),
  299.                 chrome.keyCodeListen("DOWN", null, bindFixed(editor.completeValue, editor, 1)),
  300.                 chrome.keyCodeListen("PAGE_UP", null, bindFixed(editor.completeValue, editor, -pageAmount)),
  301.                 chrome.keyCodeListen("PAGE_DOWN", null, bindFixed(editor.completeValue, editor, pageAmount))
  302.             );
  303.         }
  304.  
  305.         if (currentEditor.tabNavigation)
  306.         {
  307.             this.listeners.push(
  308.                 chrome.keyCodeListen("RETURN", null, bind(this.tabNextEditor, this)),
  309.                 chrome.keyCodeListen("RETURN", isControl, bind(this.insertRow, this, null, "after")),
  310.                 chrome.keyCodeListen("TAB", null, bind(this.tabNextEditor, this)),
  311.                 chrome.keyCodeListen("TAB", isShift, bind(this.tabPreviousEditor, this))
  312.             );
  313.         }
  314.         else if (currentEditor.multiLine)
  315.         {
  316.             this.listeners.push(
  317.                 chrome.keyCodeListen("TAB", null, insertTab)
  318.             );
  319.         }
  320.         else
  321.         {
  322.             this.listeners.push(
  323.                 chrome.keyCodeListen("RETURN", null, bindFixed(this.stopEditing, this))
  324.             );
  325.  
  326.             if (currentEditor.tabCompletion)
  327.             {
  328.                 this.listeners.push(
  329.                     chrome.keyCodeListen("TAB", null, bind(editor.completeValue, editor, 1)),
  330.                     chrome.keyCodeListen("TAB", isShift, bind(editor.completeValue, editor, -1))
  331.                 );
  332.             }
  333.         }
  334.     },
  335.  
  336.     detachListeners: function(editor, context)
  337.     {
  338.         if (!this.listeners)
  339.             return;
  340.  
  341.         var win = currentTarget.ownerDocument.defaultView;
  342.         win.removeEventListener("resize", this.onResize, true);
  343.         win.removeEventListener("blur", this.onBlur, true);
  344.  
  345.         var chrome = Firebug.chrome;
  346.         if (chrome)
  347.         {
  348.             for (var i = 0; i < this.listeners.length; ++i)
  349.                 chrome.keyIgnore(this.listeners[i]);
  350.         }
  351.  
  352.         delete this.listeners;
  353.     },
  354.  
  355.     onResize: function(event)
  356.     {
  357.         currentEditor.layout(true);
  358.     },
  359.  
  360.     onBlur: function(event)
  361.     {
  362.         if (currentEditor.enterOnBlur && isAncestor(event.target, currentEditor.box))
  363.             this.stopEditing();
  364.     },
  365.  
  366.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  367.     // extends Module
  368.  
  369.     initialize: function()
  370.     {
  371.         Firebug.Module.initialize.apply(this, arguments);
  372.  
  373.         this.onResize = bindFixed(this.onResize, this);
  374.         this.onBlur = bind(this.onBlur, this);
  375.     },
  376.  
  377.     disable: function()
  378.     {
  379.         this.stopEditing();
  380.     },
  381.  
  382.     showContext: function(browser, context)
  383.     {
  384.         this.stopEditing();
  385.     },
  386.  
  387.     showPanel: function(browser, panel)
  388.     {
  389.         this.stopEditing();
  390.     }
  391. });
  392.  
  393. // ************************************************************************************************
  394. // BaseEditor
  395.  
  396. Firebug.BaseEditor = extend(Firebug.MeasureBox,
  397. {
  398.     getValue: function()
  399.     {
  400.     },
  401.  
  402.     setValue: function(value)
  403.     {
  404.     },
  405.  
  406.     show: function(target, panel, value, textSize, targetSize)
  407.     {
  408.     },
  409.  
  410.     hide: function()
  411.     {
  412.     },
  413.  
  414.     layout: function(forceAll)
  415.     {
  416.     },
  417.  
  418.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  419.     // Support for context menus within inline editors.
  420.  
  421.     getContextMenuItems: function(target)
  422.     {
  423.         var items = [];
  424.         items.push({label: "Cut", commandID: "cmd_cut"});
  425.         items.push({label: "Copy", commandID: "cmd_copy"});
  426.         items.push({label: "Paste", commandID: "cmd_paste"});
  427.         return items;
  428.     },
  429.  
  430.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  431.     // Editor Module listeners will get "onBeginEditing" just before this call
  432.  
  433.     beginEditing: function(target, value)
  434.     {
  435.     },
  436.  
  437.     // Editor Module listeners will get "onSaveEdit" just after this call
  438.     saveEdit: function(target, value, previousValue)
  439.     {
  440.     },
  441.  
  442.     endEditing: function(target, value, cancel)
  443.     {
  444.         // Remove empty groups by default
  445.         return true;
  446.     },
  447.  
  448.     insertNewRow: function(target, insertWhere)
  449.     {
  450.     },
  451. });
  452.  
  453. // ************************************************************************************************
  454. // InlineEditor
  455.  
  456. Firebug.InlineEditor = function(doc)
  457. {
  458.     this.initializeInline(doc);
  459. };
  460.  
  461. Firebug.InlineEditor.prototype = domplate(Firebug.BaseEditor,
  462. {
  463.     enterOnBlur: true,
  464.     outerMargin: 8,
  465.     shadowExpand: 7,
  466.  
  467.     tag:
  468.         DIV({"class": "inlineEditor"},
  469.             DIV({"class": "textEditorTop1"},
  470.                 DIV({"class": "textEditorTop2"})
  471.             ),
  472.             DIV({"class": "textEditorInner1"},
  473.                 DIV({"class": "textEditorInner2"},
  474.                     INPUT({"class": "textEditorInner", type: "text",
  475.                         oninput: "$onInput", onkeypress: "$onKeyPress", onoverflow: "$onOverflow",
  476.                         oncontextmenu: "$onContextMenu"}
  477.                     )
  478.                 )
  479.             ),
  480.             DIV({"class": "textEditorBottom1"},
  481.                 DIV({"class": "textEditorBottom2"})
  482.             )
  483.         ),
  484.  
  485.     inputTag :
  486.         INPUT({"class": "textEditorInner", type: "text",
  487.             oninput: "$onInput", onkeypress: "$onKeyPress", onoverflow: "$onOverflow"}
  488.         ),
  489.  
  490.     expanderTag:
  491.         IMG({"class": "inlineExpander", src: "blank.gif"}),
  492.  
  493.     initialize: function()
  494.     {
  495.         this.fixedWidth = false;
  496.         this.completeAsYouType = true;
  497.         this.tabNavigation = true;
  498.         this.multiLine = false;
  499.         this.tabCompletion = false;
  500.         this.arrowCompletion = true;
  501.         this.noWrap = true;
  502.         this.numeric = false;
  503.     },
  504.  
  505.     destroy: function()
  506.     {
  507.         this.destroyInput();
  508.     },
  509.  
  510.     initializeInline: function(doc)
  511.     {
  512.         this.box = this.tag.replace({}, doc, this);
  513.         this.input = this.box.childNodes[1].firstChild.firstChild;  // XXXjjb childNode[1] required
  514.         this.expander = this.expanderTag.replace({}, doc, this);
  515.         this.initialize();
  516.     },
  517.  
  518.     destroyInput: function()
  519.     {
  520.         // XXXjoe Need to remove input/keypress handlers to avoid leaks
  521.     },
  522.  
  523.     getValue: function()
  524.     {
  525.         return this.input.value;
  526.     },
  527.  
  528.     setValue: function(value)
  529.     {
  530.         // It's only a one-line editor, so new lines shouldn't be allowed
  531.         return this.input.value = stripNewLines(value);
  532.     },
  533.  
  534.     show: function(target, panel, value, targetSize)
  535.     {
  536.         dispatch([Firebug.A11yModel], "onInlineEditorShow", [panel, this]);
  537.         this.target = target;
  538.         this.panel = panel;
  539.  
  540.         this.targetSize = targetSize;
  541.         this.targetOffset = getClientOffset(target);
  542.  
  543.         this.originalClassName = this.box.className;
  544.  
  545.         var classNames = target.className.split(" ");
  546.         for (var i = 0; i < classNames.length; ++i)
  547.             setClass(this.box, "editor-" + classNames[i]);
  548.  
  549.         // Make the editor match the target's font style
  550.         copyTextStyles(target, this.box);
  551.  
  552.         this.setValue(value);
  553.  
  554.         if (this.fixedWidth)
  555.             this.updateLayout(true);
  556.         else
  557.         {
  558.             this.startMeasuring(target);
  559.             this.textSize = this.measureInputText(value);
  560.  
  561.             // Correct the height of the box to make the funky CSS drop-shadow line up
  562.             var parent = this.input.parentNode;
  563.             if (hasClass(parent, "textEditorInner2"))
  564.             {
  565.                 var yDiff = this.textSize.height - this.shadowExpand;
  566.                 parent.style.height = yDiff + "px";
  567.                 parent.parentNode.style.height = yDiff + "px";
  568.             }
  569.  
  570.             this.updateLayout(true);
  571.         }
  572.  
  573.         this.getAutoCompleter().reset();
  574.  
  575.         panel.panelNode.appendChild(this.box);
  576.         this.input.select();
  577.  
  578.         // Insert the "expander" to cover the target element with white space
  579.         if (!this.fixedWidth)
  580.         {
  581.             copyBoxStyles(target, this.expander);
  582.  
  583.             target.parentNode.replaceChild(this.expander, target);
  584.             collapse(target, true);
  585.             this.expander.parentNode.insertBefore(target, this.expander);
  586.         }
  587.  
  588.         scrollIntoCenterView(this.box, null, true);
  589.     },
  590.  
  591.     hide: function()
  592.     {
  593.         this.box.className = this.originalClassName;
  594.  
  595.         if (!this.fixedWidth)
  596.         {
  597.             this.stopMeasuring();
  598.  
  599.             collapse(this.target, false);
  600.  
  601.             if (this.expander.parentNode)
  602.                 this.expander.parentNode.removeChild(this.expander);
  603.         }
  604.  
  605.         if (this.box.parentNode)
  606.         {
  607.             try { this.input.setSelectionRange(0, 0); } catch (exc) {}
  608.             this.box.parentNode.removeChild(this.box);
  609.         }
  610.  
  611.         delete this.target;
  612.         delete this.panel;
  613.     },
  614.  
  615.     layout: function(forceAll)
  616.     {
  617.         if (!this.fixedWidth)
  618.             this.textSize = this.measureInputText(this.input.value);
  619.  
  620.         if (forceAll)
  621.             this.targetOffset = getClientOffset(this.expander);
  622.  
  623.         this.updateLayout(false, forceAll);
  624.     },
  625.  
  626.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  627.  
  628.     beginEditing: function(target, value)
  629.     {
  630.     },
  631.  
  632.     saveEdit: function(target, value, previousValue)
  633.     {
  634.     },
  635.  
  636.     endEditing: function(target, value, cancel)
  637.     {
  638.         // Remove empty groups by default
  639.         return true;
  640.     },
  641.  
  642.     insertNewRow: function(target, insertWhere)
  643.     {
  644.     },
  645.  
  646.     advanceToNext: function(target, charCode)
  647.     {
  648.         return false;
  649.     },
  650.  
  651.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  652.  
  653.     getAutoCompleteRange: function(value, offset)
  654.     {
  655.     },
  656.  
  657.     getAutoCompleteList: function(preExpr, expr, postExpr)
  658.     {
  659.     },
  660.  
  661.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  662.  
  663.     getAutoCompleter: function()
  664.     {
  665.         if (!this.autoCompleter)
  666.         {
  667.             this.autoCompleter = new Firebug.AutoCompleter(null,
  668.                 bind(this.getAutoCompleteRange, this), bind(this.getAutoCompleteList, this),
  669.                 true, false);
  670.         }
  671.  
  672.         return this.autoCompleter;
  673.     },
  674.  
  675.     completeValue: function(amt)
  676.     {
  677.         if (this.getAutoCompleter().complete(currentPanel.context, this.input, true, amt < 0))
  678.             Firebug.Editor.update(true);
  679.         else
  680.             this.incrementValue(amt);
  681.     },
  682.  
  683.     incrementValue: function(amt)
  684.     {
  685.         var value = this.input.value;
  686.         var start = this.input.selectionStart, end = this.input.selectionEnd;
  687.  
  688.         var range = this.getAutoCompleteRange(value, start);
  689.         if (!range || range.type != "int")
  690.             range = {start: 0, end: value.length-1};
  691.  
  692.         var expr = value.substr(range.start, range.end-range.start+1);
  693.         preExpr = value.substr(0, range.start);
  694.         postExpr = value.substr(range.end+1);
  695.  
  696.         // See if the value is an integer, and if so increment it
  697.         var intValue = parseInt(expr);
  698.         if (!!intValue || intValue == 0)
  699.         {
  700.             var m = /\d+/.exec(expr);
  701.             var digitPost = expr.substr(m.index+m[0].length);
  702.  
  703.             var completion = intValue-amt;
  704.             this.input.value = preExpr + completion + digitPost + postExpr;
  705.             this.input.setSelectionRange(start, end);
  706.  
  707.             Firebug.Editor.update(true);
  708.  
  709.             return true;
  710.         }
  711.         else
  712.             return false;
  713.     },
  714.  
  715.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  716.  
  717.     onKeyPress: function(event)
  718.     {
  719.         if (event.keyCode == 27 && !this.completeAsYouType)
  720.         {
  721.             var reverted = this.getAutoCompleter().revert(this.input);
  722.             if (reverted)
  723.                 cancelEvent(event);
  724.         }
  725.         else if (event.charCode && this.advanceToNext(this.target, event.charCode))
  726.         {
  727.             Firebug.Editor.tabNextEditor();
  728.             cancelEvent(event);
  729.         }
  730.         else
  731.         {
  732.             if (this.numeric && event.charCode && (event.charCode < 48 || event.charCode > 57)
  733.                 && event.charCode != 45 && event.charCode != 46)
  734.                 FBL.cancelEvent(event);
  735.             else
  736.             {
  737.                 // If the user backspaces, don't autocomplete after the upcoming input event
  738.                 this.ignoreNextInput = event.keyCode == 8;
  739.             }
  740.         }
  741.     },
  742.  
  743.     onOverflow: function()
  744.     {
  745.         this.updateLayout(false, false, 3);
  746.     },
  747.  
  748.     onInput: function()
  749.     {
  750.         if (this.ignoreNextInput)
  751.         {
  752.             this.ignoreNextInput = false;
  753.             this.getAutoCompleter().reset();
  754.         }
  755.         else if (this.completeAsYouType)
  756.             this.getAutoCompleter().complete(currentPanel.context, this.input, false);
  757.         else
  758.             this.getAutoCompleter().reset();
  759.  
  760.         Firebug.Editor.update();
  761.     },
  762.  
  763.     onContextMenu: function(event)
  764.     {
  765.         cancelEvent(event);
  766.  
  767.         var popup = $("fbInlineEditorPopup");
  768.         FBL.eraseNode(popup);
  769.  
  770.         var target = event.target;
  771.         var menu = this.getContextMenuItems(target);
  772.         if (menu)
  773.         {
  774.             for (var i = 0; i < menu.length; ++i)
  775.                 FBL.createMenuItem(popup, menu[i]);
  776.         }
  777.  
  778.         if (!popup.firstChild)
  779.             return false;
  780.  
  781.         popup.openPopupAtScreen(event.screenX, event.screenY, true);
  782.         return true;
  783.     },
  784.  
  785.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  786.  
  787.     updateLayout: function(initial, forceAll, extraWidth)
  788.     {
  789.         if (this.fixedWidth)
  790.         {
  791.             this.box.style.left = (this.targetOffset.x) + "px";
  792.             this.box.style.top = (this.targetOffset.y) + "px";
  793.  
  794.             var w = this.target.offsetWidth;
  795.             var h = this.target.offsetHeight;
  796.             this.input.style.width = w + "px";
  797.             this.input.style.height = (h-3) + "px";
  798.         }
  799.         else
  800.         {
  801.             if (initial || forceAll)
  802.             {
  803.                 this.box.style.left = this.targetOffset.x + "px";
  804.                 this.box.style.top = this.targetOffset.y + "px";
  805.             }
  806.  
  807.             var approxTextWidth = this.textSize.width;
  808.             var maxWidth = (currentPanel.panelNode.scrollWidth - this.targetOffset.x)
  809.                 - this.outerMargin;
  810.  
  811.             var wrapped = initial
  812.                 ? this.noWrap && this.targetSize.height > this.textSize.height+3
  813.                 : this.noWrap && approxTextWidth > maxWidth;
  814.  
  815.             if (wrapped)
  816.             {
  817.                 var style = this.target.ownerDocument.defaultView.getComputedStyle(this.target, "");
  818.                 targetMargin = parseInt(style.marginLeft) + parseInt(style.marginRight);
  819.  
  820.                 // Make the width fit the remaining x-space from the offset to the far right
  821.                 approxTextWidth = maxWidth - targetMargin;
  822.  
  823.                 this.input.style.width = "100%";
  824.                 this.box.style.width = approxTextWidth + "px";
  825.             }
  826.             else
  827.             {
  828.                 // Make the input one character wider than the text value so that
  829.                 // typing does not ever cause the textbox to scroll
  830.                 var charWidth = this.measureInputText('m').width;
  831.  
  832.                 // Sometimes we need to make the editor a little wider, specifically when
  833.                 // an overflow happens, otherwise it will scroll off some text on the left
  834.                 if (extraWidth)
  835.                     charWidth *= extraWidth;
  836.  
  837.                 var inputWidth = approxTextWidth + charWidth;
  838.  
  839.                 if (initial)
  840.                     this.box.style.width = "auto";
  841.                 else
  842.                 {
  843.                     var xDiff = this.box.scrollWidth - this.input.offsetWidth;
  844.                     this.box.style.width = (inputWidth + xDiff) + "px";
  845.                 }
  846.  
  847.                 this.input.style.width = inputWidth + "px";
  848.             }
  849.  
  850.             this.expander.style.width = approxTextWidth + "px";
  851.             this.expander.style.height = (this.textSize.height-3) + "px";
  852.         }
  853.  
  854.         if (forceAll)
  855.             scrollIntoCenterView(this.box, null, true);
  856.     }
  857. });
  858.  
  859. // ************************************************************************************************
  860. // Autocompletion
  861.  
  862. Firebug.AutoCompleter = function(getExprOffset, getRange, evaluator, selectMode, caseSensitive)
  863. {
  864.     var candidates = null;
  865.     var originalValue = null;
  866.     var originalOffset = -1;
  867.     var lastExpr = null;
  868.     var lastOffset = -1;
  869.     var exprOffset = 0;
  870.     var lastIndex = 0;
  871.     var preParsed = null;
  872.     var preExpr = null;
  873.     var postExpr = null;
  874.  
  875.     this.revert = function(textBox)
  876.     {
  877.         if (originalOffset != -1)
  878.         {
  879.             textBox.value = originalValue;
  880.             textBox.setSelectionRange(originalOffset, originalOffset);
  881.  
  882.             this.reset();
  883.             return true;
  884.         }
  885.         else
  886.         {
  887.             this.reset();
  888.             return false;
  889.         }
  890.     };
  891.  
  892.     this.reset = function()
  893.     {
  894.         candidates = null;
  895.         originalValue = null;
  896.         originalOffset = -1;
  897.         lastExpr = null;
  898.         lastOffset = 0;
  899.         exprOffset = 0;
  900.     };
  901.  
  902.     this.complete = function(context, textBox, cycle, reverse)
  903.     {
  904.         var value = lastValue = textBox.value;
  905.         var offset = textBox.selectionStart;
  906.         if (!selectMode && originalOffset != -1)
  907.             offset = originalOffset;
  908.  
  909.         if (!candidates || !cycle || offset != lastOffset)
  910.         {
  911.             originalOffset = offset;
  912.             originalValue = value;
  913.  
  914.             // Find the part of the string that will be parsed
  915.             var parseStart = getExprOffset ? getExprOffset(value, offset, context) : 0;
  916.             preParsed = value.substr(0, parseStart);
  917.             var parsed = value.substr(parseStart);
  918.  
  919.             // Find the part of the string that is being completed
  920.             var range = getRange ? getRange(parsed, offset-parseStart, context) : null;
  921.             if (!range)
  922.                 range = {start: 0, end: parsed.length-1 };
  923.  
  924.             var expr = parsed.substr(range.start, range.end-range.start+1);
  925.             preExpr = parsed.substr(0, range.start);
  926.             postExpr = parsed.substr(range.end+1);
  927.             exprOffset = parseStart + range.start;
  928.  
  929.             if (!cycle)
  930.             {
  931.                 if (!expr)
  932.                     return;
  933.                 else if (lastExpr && lastExpr.indexOf(expr) != 0)
  934.                 {
  935.                     candidates = null;
  936.                 }
  937.                 else if (lastExpr && lastExpr.length >= expr.length)
  938.                 {
  939.                     candidates = null;
  940.                     lastExpr = expr;
  941.                     return;
  942.                 }
  943.             }
  944.  
  945.             lastExpr = expr;
  946.             lastOffset = offset;
  947.  
  948.             var searchExpr;
  949.  
  950.             // Check if the cursor is at the very right edge of the expression, or
  951.             // somewhere in the middle of it
  952.             if (expr && offset != parseStart+range.end+1)
  953.             {
  954.                 if (cycle)
  955.                 {
  956.                     // We are in the middle of the expression, but we can
  957.                     // complete by cycling to the next item in the values
  958.                     // list after the expression
  959.                     offset = range.start;
  960.                     searchExpr = expr;
  961.                     expr = "";
  962.                 }
  963.                 else
  964.                 {
  965.                     // We can't complete unless we are at the ridge edge
  966.                     return;
  967.                 }
  968.             }
  969.  
  970.             var values = evaluator(preExpr, expr, postExpr, context);
  971.             if (!values)
  972.                 return;
  973.  
  974.             if (expr)
  975.             {
  976.                 // Filter the list of values to those which begin with expr. We
  977.                 // will then go on to complete the first value in the resulting list
  978.                 candidates = [];
  979.  
  980.                 if (caseSensitive)
  981.                 {
  982.                     for (var i = 0; i < values.length; ++i)
  983.                     {
  984.                         var name = values[i];
  985.                         if (name.indexOf && name.indexOf(expr) == 0)
  986.                             candidates.push(name);
  987.                     }
  988.                 }
  989.                 else
  990.                 {
  991.                     var lowerExpr = caseSensitive ? expr : expr.toLowerCase();
  992.                     for (var i = 0; i < values.length; ++i)
  993.                     {
  994.                         var name = values[i];
  995.                         if (name.indexOf && name.toLowerCase().indexOf(lowerExpr) == 0)
  996.                             candidates.push(name);
  997.                     }
  998.                 }
  999.  
  1000.                 lastIndex = reverse ? candidates.length-1 : 0;
  1001.             }
  1002.             else if (searchExpr)
  1003.             {
  1004.                 var searchIndex = -1;
  1005.  
  1006.                 // Find the first instance of searchExpr in the values list. We
  1007.                 // will then complete the string that is found
  1008.                 if (caseSensitive)
  1009.                 {
  1010.                     searchIndex = values.indexOf(expr);
  1011.                 }
  1012.                 else
  1013.                 {
  1014.                     var lowerExpr = searchExpr.toLowerCase();
  1015.                     for (var i = 0; i < values.length; ++i)
  1016.                     {
  1017.                         var name = values[i];
  1018.                         if (name && name.toLowerCase().indexOf(lowerExpr) == 0)
  1019.                         {
  1020.                             searchIndex = i;
  1021.                             break;
  1022.                         }
  1023.                     }
  1024.                 }
  1025.  
  1026.                 // Nothing found, so there's nothing to complete to
  1027.                 if (searchIndex == -1)
  1028.                     return this.reset();
  1029.  
  1030.                 expr = searchExpr;
  1031.                 candidates = cloneArray(values);
  1032.                 lastIndex = searchIndex;
  1033.             }
  1034.             else
  1035.             {
  1036.                 expr = "";
  1037.                 candidates = [];
  1038.                 for (var i = 0; i < values.length; ++i)
  1039.                 {
  1040.                     if (values[i].substr)
  1041.                         candidates.push(values[i]);
  1042.                 }
  1043.                 lastIndex = -1;
  1044.             }
  1045.         }
  1046.  
  1047.         if (cycle)
  1048.         {
  1049.             expr = lastExpr;
  1050.             lastIndex += reverse ? -1 : 1;
  1051.         }
  1052.  
  1053.         if (!candidates.length)
  1054.             return;
  1055.  
  1056.         if (lastIndex >= candidates.length)
  1057.             lastIndex = 0;
  1058.         else if (lastIndex < 0)
  1059.             lastIndex = candidates.length-1;
  1060.  
  1061.         var completion = candidates[lastIndex];
  1062.         var preCompletion = expr.substr(0, offset-exprOffset);
  1063.         var postCompletion = completion.substr(offset-exprOffset);
  1064.  
  1065.         textBox.value = preParsed + preExpr + preCompletion + postCompletion + postExpr;
  1066.         var offsetEnd = preParsed.length + preExpr.length + completion.length;
  1067.         if (selectMode)
  1068.             textBox.setSelectionRange(offset, offsetEnd);
  1069.         else
  1070.             textBox.setSelectionRange(offsetEnd, offsetEnd);
  1071.  
  1072.         return true;
  1073.     };
  1074. };
  1075.  
  1076. // ************************************************************************************************
  1077. // Local Helpers
  1078.  
  1079. function getDefaultEditor(panel)
  1080. {
  1081.     if (!defaultEditor)
  1082.     {
  1083.         var doc = panel.document;
  1084.         defaultEditor = new Firebug.InlineEditor(doc);
  1085.     }
  1086.  
  1087.     return defaultEditor;
  1088. }
  1089.  
  1090. /**
  1091.  * An outsider is the first element matching the stepper element that
  1092.  * is not an child of group. Elements tagged with insertBefore or insertAfter
  1093.  * classes are also excluded from these results unless they are the sibling
  1094.  * of group, relative to group's parent editGroup. This allows for the proper insertion
  1095.  * rows when groups are nested.
  1096.  */
  1097. function getOutsider(element, group, stepper)
  1098. {
  1099.     var parentGroup = getAncestorByClass(group.parentNode, "editGroup");
  1100.     var next;
  1101.     do
  1102.     {
  1103.         next = stepper(next || element);
  1104.     }
  1105.     while (isAncestor(next, group) || isGroupInsert(next, parentGroup));
  1106.  
  1107.     return next;
  1108. }
  1109.  
  1110. function isGroupInsert(next, group)
  1111. {
  1112.     return (!group || isAncestor(next, group))
  1113.         && (hasClass(next, "insertBefore") || hasClass(next, "insertAfter"));
  1114. }
  1115.  
  1116. function getNextOutsider(element, group)
  1117. {
  1118.     return getOutsider(element, group, bind(getNextByClass, FBL, "editable"));
  1119. }
  1120.  
  1121. function getPreviousOutsider(element, group)
  1122. {
  1123.     return getOutsider(element, group, bind(getPreviousByClass, FBL, "editable"));
  1124. }
  1125.  
  1126. function getInlineParent(element)
  1127. {
  1128.     var lastInline = element;
  1129.     for (; element; element = element.parentNode)
  1130.     {
  1131.         var s = element.ownerDocument.defaultView.getComputedStyle(element, "");
  1132.         if (s.display != "inline")
  1133.             return lastInline;
  1134.         else
  1135.             lastInline = element;
  1136.     }
  1137.     return null;
  1138. }
  1139.  
  1140. function insertTab()
  1141. {
  1142.     insertTextIntoElement(currentEditor.input, Firebug.Editor.tabCharacter);
  1143. }
  1144.  
  1145. // ************************************************************************************************
  1146.  
  1147. Firebug.registerModule(Firebug.Editor);
  1148.  
  1149. // ************************************************************************************************
  1150.  
  1151. }});
  1152.